Fix #14105: Inconsistent type inference for anonymous functions#4913
Closed
phpstan-bot wants to merge 1 commit into2.1.xfrom
Closed
Fix #14105: Inconsistent type inference for anonymous functions#4913phpstan-bot wants to merge 1 commit into2.1.xfrom
phpstan-bot wants to merge 1 commit into2.1.xfrom
Conversation
- InitializerExprTypeResolver now infers closure return types from the body - Builds parameter variable type map and passes it through expression resolution - Uses intersectButNotNever() to combine inferred return type with annotation - New regression test in tests/PHPStan/Analyser/nsrt/bug-14105.php Closes phpstan/phpstan#14105
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PHPStan inferred different return types for identical anonymous functions depending on where they were defined. A closure assigned to a class constant lost its precise return type (e.g.,
Closure(int): arrayinstead ofClosure(int): array{num: int}), while the same closure assigned to a top-level constant was inferred correctly.Changes
src/Reflection/InitializerExprTypeResolver.php:inferClosureReturnType()- collects return expressions from closure body and resolves their typesresolveExprTypeWithVariables()- resolves expressions with awareness of closure parameter types (variables normally fall through toMixedTypeinInitializerExprTypeResolver)collectReturnExpressions()- recursively walks statement nodes to find return statements, skipping nested closures/functions/classesintersectButNotNever()- intersects declared return type annotation with inferred return type (ported fromMutatingScope)tests/PHPStan/Analyser/nsrt/bug-14105.phpCLAUDE.mdwith documentation about this patternRoot cause
InitializerExprTypeResolveris used to resolve types for constant expressions (class constant initializers, etc.) without a full analysis scope. For static closures, it only read the return type annotation (e.g.,array) without analyzing the closure body. In contrast,MutatingScope::getClosureType()performs full body analysis during the main analysis phase, which is why top-level constants (resolved through the normal analysis pipeline) got the correct precise type.The fix adds lightweight closure body analysis to
InitializerExprTypeResolver: it collects return expressions, resolves their types using parameter type information, and intersects the result with the declared return type annotation to produce a more precise type.Test
The regression test in
tests/PHPStan/Analyser/nsrt/bug-14105.phpverifies that both class-level and top-level constants with closure values are inferred asClosure(int): array{num: int}.Fixes phpstan/phpstan#14105